<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>贪吃蛇小游戏</title>
	</head>

	<body>
		<style>
			* {
				padding: 0;
				margin: 0;
				box-sizing: border-box;
			}

			#map {
				margin: 100px auto;
				border: 1px solid #ccc;
				position: relative;
				display: grid;
				grid-template-columns: repeat(20, 1fr);
				grid-template-rows: repeat(20, 1fr);
				background: white;
				background-image: linear-gradient(
						90deg,
						rgba(200, 0, 0, 0.5) 50%,
						transparent 0
					),
					linear-gradient(rgba(200, 0, 0, 0.5) 50%, transparent 0);
				background-size: 30px 30px;

				/* background: #58a;
          background-image: linear-gradient(white 1px, transparent 0),
            linear-gradient(90deg, white 1px, transparent 0),
            linear-gradient(hsla(0, 0%, 100%, 0.3) 1px, transparent 0),
            linear-gradient(90deg, hsla(0, 0%, 100%, 0.3) 1px, transparent 0);
          background-size: 20px 20px; */
			}

			span {
				position: absolute;
				position: absolute;
				display: inline-block;
				width: 20px;
				height: 20px;
				background-color: #ffc312;
				border: 1px solid #eee;
			}

			.head {
				border-radius: 4px;
				background-color: red;
			}

			.head::after {
				content: '.';
				position: absolute;
				display: inline-block;
				width: 1px;
				height: 1px;
				top: 0;
				left: 50%;
				transform: translate(-50%, -50%);
			}

			.head::before {
				content: '.';
				position: absolute;
				display: inline-block;
				width: 1px;
				height: 1px;
				top: -50%;
				left: 50%;
				transform: translate(-50%, -50%);
			}

			.food {
				background: rebeccapurple;
			}

			.title {
				text-align: center;
				font-size: 1.5em;
				margin-top: 30px;
			}

			.score {
				text-align: center;
				font-size: 1.2em;
				margin-top: 30px;
				font-weight: 600;
			}
		</style>
		<div class="title">按下任意键即可开始,上下左右控制方向</div>
		<div class="score"></div>
		<div id="map"></div>
	</body>
	<script>
		let that
		class GluttonousSnake {
			constructor(name, time) {
				that = this
				this.map = document.querySelector(name)
				this.MAP_HEIGHT = 20 //地图高度
				this.MAP_WIDTH = 20 // 地图宽度
				this.SNACK_SIZE = 20 // 蛇的大小
				this.snakeHead = [4, 5] // 蛇头
				this.snakeBody = [
					//蛇身
					[3, 5],
					[2, 5],
					[1, 5],
					[0, 5],
				]
				this.food = [8, 9] // 食物
				this.direction = [1, 0] // 方向 x :1 为右边 y:1为向下 （绝对定位，待会坐标top值越大位置越向下）
				this.timer = null // 定时器
				this.timeSpped = time
				this.score = 0 // 初始分数
				this.init()
			}
			// 构建地图
			init() {
				this.map.style.width = this.MAP_WIDTH * this.SNACK_SIZE + 'px'
				this.map.style.height = this.MAP_HEIGHT * this.SNACK_SIZE + 'px'
				//创建蛇头
				this.createElements(this.snakeHead[0], this.snakeHead[1], 'head')
				// 创建蛇身
				this.createSnakeBody()
				// 添加蛇头进蛇身
				this.snakeBody.unshift(this.snakeHead)
				// 创建食物
				this.createElements(this.food[0], this.food[1], 'food')
				// 监听键盘事件
				document.addEventListener('keyup', this.handlKeyuo)
			}
			// 开始游戏
			startGam() {
				clearInterval(that.timer)
				this.timer = setInterval(() => {
					// 先创建蛇再移动 会丢失两节蛇身
					this.createSnakeBody()
					this.handelMove()
				}, this.timeSpped)
			}
			// 创建单个元素
			createElements(x, y, className, callback) {
				const span = document.createElement('span')
				if (className) {
					// 为了创建食物
					span.classList.add(className)
				}
				if (this.snakeHead[0] === x && this.snakeHead[1] === y) {
					// 为了单独创建蛇头
					span.classList.add('head')
				}
				span.style.left = x * this.MAP_WIDTH + 'px'
				span.style.top = y * this.MAP_HEIGHT + 'px'
				span.setAttribute('data-point', [x, y])
				this.map.appendChild(span)
				callback && callback()
			}
			// 创建蛇身
			createSnakeBody() {
				this.snakeBody.forEach((item) => {
					this.createElements(item[0], item[1], null, function () {
						//  创建一个 ，删除数组中的最后一个
						that.clearSnacke()
					})
				})
			}
			// 清除一个元素
			clearSnacke() {
				// 循环清除数组最后一个元素
				that.map.childNodes.forEach((node) => {
					if (node.nodeType === 1) {
						const attributes = node.attributes
						Array.from(node.attributes).forEach((attr) => {
							if (attr.nodeName === 'data-point') {
								const str = attr.nodeValue.split(',')
								// 找到了末尾节点
								const length = that.snakeBody.length
								if (
									str[0] == that.snakeBody[length - 1][0] &&
									str[1] == that.snakeBody[length - 1][1]
								) {
									node.parentNode.removeChild(node)
								}
							}
						})
					}
				})
			}
			handlKeyuo(e) {
				// 按下按键开始游戏
				that.startGam()
				// 上 38    下 40    左 37    右 39
				const code = e.keyCode
				// 游戏设定 蛇是无法在同一个方向吃到自己的
				switch (code) {
					case 38:
						if (that.direction[1] != 1) that.direction = [0, -1]
						break
					case 40:
						if (that.direction[1] != -1) that.direction = [0, 1]
						break
					case 37:
						if (that.direction[0] != 1) that.direction = [-1, 0]
						break
					case 39:
						if (that.direction[0] != -1) that.direction = [1, 0]
						break
				}
			}
			// 处理蛇身移动 后一节等于前一节的位置
			handelMove() {
				//蛇身移动
				that.eatFood() // 食物检测
				that.isEatSelf() // 边界检测
				that.isArrivedBorder() // 吃到自己检测
				for (let i = that.snakeBody.length - 1; i > 0; i--) {
					that.snakeBody[i][0] = that.snakeBody[i - 1][0]
					that.snakeBody[i][1] = that.snakeBody[i - 1][1]
				}
				// 蛇头移动
				that.snakeBody[0][0] += that.direction[0]
				that.snakeBody[0][1] += that.direction[1]
				// 保存最新蛇头的坐标
				that.snakeHead = [that.snakeBody[0][0], that.snakeBody[0][1]]
			}
			//创建随机食物
			createdFood(x, y, foodName) {
				this.createElements(x, y, foodName)
			}
			// 检测食物是否合理 -- 蛇身不能是食物
			cheackFood() {
				this.snakeBody.forEach((item) => {
					// 蛇身存在食物
					if (item[0] === that.food[0] && item[1] === that.food[1]) {
						// 重现生成食物坐标
						that.ramdom()
					}
				})
				// 创建新的食物
				that.createdFood(that.food[0], that.food[1], 'food')
			}
			//随机食物
			ramdom(n = 0, m = 19) {
				that.food[0] = Math.floor(Math.random() * (m - n + 1) + n)
				that.food[1] = Math.floor(Math.random() * (m - n + 1) + n)

				that.cheackFood()
			}
			// 蛇是否吃到食物
			eatFood() {
				//吃到食物
				if (
					that.snakeHead[0] === that.food[0] &&
					that.snakeHead[1] === that.food[1]
				) {
					that.score++
					// 蛇身加1
					const length = this.snakeBody.length
					that.createElements(
						that.snakeBody[length - 1][0],
						that.snakeBody[length - 1][1],
						null
					)
					// 计算新产生蛇的身体的坐标
					const x = that.snakeBody[length - 1][0] * this.SNACK_SIZE
					const y = that.snakeBody[length - 1][0] * this.SNACK_SIZE
					console.log('新坐标', x, y)

					that.snakeBody.push([x, y])
					// 产生新的食物
					that.ramdom()
				}
			}
			// 边界检测 蛇头位置
			isArrivedBorder() {
				if (
					this.snakeHead[0] >= this.MAP_WIDTH ||
					this.snakeHead[0] < 0 ||
					this.snakeHead[1] >= this.MAP_HEIGHT ||
					this.snakeHead[1] < 0
				) {
					clearInterval(that.timer)
					alert(`游戏结束, 得分:${that.score}`)
					that.gameOver()
				}
			}
			// 吃到自己检测 蛇头和蛇身有重叠
			isEatSelf() {
				for (let i = 1; i < that.snakeBody.length; i++) {
					console.log(that.snakeBody[i])
					if (
						that.snakeBody[i][0] === that.snakeHead[0] &&
						that.snakeBody[i][1] === that.snakeHead[1]
					) {
						alert(`吃到自己,得分:${that.score}`)
						that.gameOver()
					}
				}
			}
			// 游戏结束
			gameOver() {
				// 清除定时器
				clearInterval(that.timer)
				location.reload()
			}
		}

		// 实例化
		const gam = new GluttonousSnake('#map', 100)
		setInterval(() => {
			document.querySelector('.score').innerHTML = `得分:${gam.score}`
		}, 100)
	</script>
</html>
<!-- 
基本逻辑以及步骤
1. 初始化 创建地图的规格，得分、定时器执行频率
2. 创建 蛇头、蛇身、食物
 + 蛇头后续要进行、移动、边界、食物、自己吃自己等判端(单独设置出来更方便)
 + 蛇身采用二维数组，每一个存放x,y轴的坐标，蛇身创建完成后添加蛇头进数组[0][0]位置
 + 食物坐标采用随机数（位置：地图范围内，排除蛇本身坐标）
3.设置蛇头的初始方向、按键改变蛇头方向
4. 开启定时器，蛇身移动
+ 4.1 移动规则：后一节走前一节的路线（后一个坐标赋值个前一个坐标）
+ 4.12 实现逻辑，每次向前就创建一个新span添加到页面，同时删除最后的span让长度不变
+ 4.3 蛇头移动规则 ：蛇头坐标 +=方向坐标，保存蛇头最新位置
5.监听键盘事件改变蛇头方向 // 上 38    下 40    左 37    右 39
+ 5.1 改变蛇头规则：如 现在要向上，之前不能是向下,不热会吃到自己 
6. 碰撞检测--是否吃到食物
+ 6.1 蛇头坐标等于食物坐标即吃到食物，得分加1
7. 碰撞检测---是否超出范围 
+ 7.1 蛇头坐标X，y与四周的边界值比较
8. 身体边长
+ 8.1 根据蛇身最后一节坐标计算新产生的身体的坐标
+ 8.2 将新的坐标放到蛇身数组中
+ 8.3 产生新的食物（对食物进行位置检测）
9 结束游戏
 -->
